package com.sd365.gateway.authentication.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.sd365.common.core.common.exception.BusinessException;
import com.sd365.common.core.common.exception.code.ThirdPartyApiErrorCode;
import com.sd365.common.util.JwtTokenBuilder;
import com.sd365.gateway.authentication.service.AuthenticationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotBlank;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
/**
 *  认证服务以下功能：
 *  1 调用用户中心返回认证的结果
 *  2 调用工具类生成JWT token
 *  3  设置 token的续约
 */
@Slf4j
@RefreshScope
@Service
public class AuthenticationServiceImpl implements AuthenticationService {

    /**
     * Jwt Token签名秘钥
     */
    @Value("${app.secret.token.key}")
    private String secret="$6$bosssoft$";
    /**
     * common包中封装的用于jwttoken构建和签名
     */
    @Autowired
    private JwtTokenBuilder jwtTokenBuilder;
    /**
     * 定义了认证接口的URL
     */
    public static final String AUTH_URL = "http://sd365-permission-center/permission/centre/v1/user/auth?code=%s&account=%s&password=%s";
    /**
     * RedisTemplateConfig 中生成
     */
    @Resource(name = "tokenRedisTemplate")
    private RedisTemplate redisTemplate;

    /**
     * CurrentServiceTemplateBeanConfig.java中定义
     */
    @Autowired
    private RestTemplate restTemplate;

    /**
     * 默认设置10天的token有效期
     */
    @Value("${app.secret.token.period}")
    private Long period = 864000L;
    /**
     * 默认设置一天的提前提醒时间 举例如果有效期10天则在第9天提醒
     */
    @Value("${app.secret.token.remind}")
    private Long remind=86400L;
    /**
     * 设置 user的token的key
     */
    private static final String TOKEN_USER_ID_KEY = "remote:cache:token:user:";

    /**
     *  公共应答体body部分就是包含 UserVO
     */
    private static final String COMMON_RESPONSE_BODY="body";

    /**
     *  公共应答体head部分
     */
    private static final String COMMON_RESPONSE_HEAD="head";

    /**
     *  公共应答体head的code部分
     */
    private static final String COMMON_RESPONSE_HEAD_CODE="code";

    /**
     *  公共应答体head的code部分
     */
    private static final String COMMON_RESPONSE_HEAD_CODE_SUCCESS="0";
    /**
     *  公共应答体head部分
     */
    private static final String COMMON_RESPONSE_CODE_VALUE="head";
    /**
     *  公共应答体message部分
     */
    private static final String COMMON_RESPONSE_MESSAGE="message";
    /**
     *  公共应答体的UserVO的 code
     */
    private static final String COMMON_RESPONSE_BODY_CODE="code";

    /**
     * 1.验证租户，账号，密码是否正确
                * 2.验证成功则生成对应的jwtToken
                * 3.把用户id信息加入token，在网关时鉴权需要用到
                * @param code
                * @param account
                * @param password
                * @return
     */
        @Override
        public HashMap doAuth(@NotBlank String code, @NotBlank String account, @NotBlank String password) {
            // 用户中心返回的CommonResponse 包含 UserVO对象
            HashMap authResult=getUserFromPermissionCentre(code,account,password);
        // 输出一下键值信息
        // 取出头部信息
        HashMap head = (HashMap) authResult.get("head");
        // 取出头部信息里的登录码
        String codeAuth = (String) head.get("code");

        // 不为0则出错
        if(!codeAuth.equals("0")){
            throw new BusinessException(ThirdPartyApiErrorCode.INNER_CALL_ERROR,
                    String.format("用户中心发生认证错误(%s)",((HashMap)authResult).get(COMMON_RESPONSE_MESSAGE)));
        }
        // 如果用户中心已返回错误码则将错抛出改层统一异常处理
//        if(!((HashMap)authResult.get(COMMON_RESPONSE_HEAD)).get(COMMON_RESPONSE_HEAD_CODE).equals(COMMON_RESPONSE_HEAD_CODE_SUCCESS)){
//            throw new BusinessException(ThirdPartyApiErrorCode.INNER_CALL_ERROR,
//                    String.format("用户中心发生认证错误(%s)",((HashMap)authResult).get(COMMON_RESPONSE_MESSAGE)));
//        }
        // 返回签名后的jwt 采用标准的 . 号隔离
        String token = createJwtToken(code, account, authResult);
        // 打印日志
        log.debug("token="+token);
        // 将签名后的token带回
        ((HashMap)((HashMap)authResult.get("body")).get("data")).put("token",token);
        // 获取用户id 设置token有效期
        renewalToken(((HashMap<String,String>)authResult.get("body")).get("id"),token);
        return authResult;
    }

    /**
     *  调用用户中心完成人员认证
     * @param code 工号
     * @param account 账号
     * @param password 密码
     */
    private HashMap getUserFromPermissionCentre(String code,String account,String password){
        try {
            return restTemplate.exchange(String.format(AUTH_URL, code, account, password), HttpMethod.GET, null, HashMap.class).getBody();
        }catch (Exception ex){
            throw new BusinessException(ThirdPartyApiErrorCode.INNER_CALL_ERROR,"请求用户中心发生网络异常",ex);
        }
    }

    /**
     * 根据工号、账号、密码生成jwt令牌
     * jwt令牌分为三部分：
     * 1.head
     * 2.payLoad
     * 3.signature
     */
    private String createJwtToken(String code, String account, HashMap authResult) {
        /**
         *  用户中心返回new UserVO  ，UserVO的code值 1 代表认证通过 0 代表租户错误 2 代码账号或者密码错误
         */
        // map中包含信息，全部取出
        HashMap body = (HashMap) authResult.get(COMMON_RESPONSE_BODY);// 这个就是 CommonResponse的body UserVO
        // 即是UserVO中的code
        Integer businessCode=(Integer) (body.get(COMMON_RESPONSE_BODY_CODE));
        JSONObject header=createJwtHead(JwtTokenBuilder.ALG_SHA2,"JWT");
        JSONObject payLoad= createJwtPayLoad(body,code,account);
        // 利用工具类生成token，返回的报文带了JWT规范加密后的token(head,payLoad,signature)
        return  jwtTokenBuilder.build().buildHead(header).buildPayload(payLoad)
                .buildSignature(secret).toString();
    }


    /**
     * 创建 jwttoken负载
     * @param commonResponseBody
     * @param code 登录工号
     * @param account 登录租户账号
     */
    private JSONObject createJwtPayLoad(HashMap commonResponseBody, String code , String account){
        // 把payLoad的参数全部加入对象中
        final JSONObject payLoad = new JSONObject();
        payLoad.put("account", account);
        payLoad.put("roleIds",  (ArrayList) (commonResponseBody).get("roleIds"));
        payLoad.put("tenantId", Long.parseLong((String) commonResponseBody.getOrDefault("tenantId", "-1")));
        payLoad.put("companyId", Long.parseLong((String) commonResponseBody.getOrDefault("companyId", "-1")));
        payLoad.put("orgId", Long.parseLong((String) commonResponseBody.getOrDefault("orgId", "-1")));
        payLoad.put("userId", Long.parseLong((String) commonResponseBody.getOrDefault("id", "-1")));
        payLoad.put("userName", (String) commonResponseBody.getOrDefault("name", "-1"));
        payLoad.put("code", code);
        // 设置token 3天过期
        payLoad.put("expiresAt",new Date(System.currentTimeMillis() + period*1000));
        return payLoad;
    }

    /**
     *  创建token head
     * @param alg 算法
     * @param type token类型
     * @return  JwtToken Heawd
     */
    private JSONObject createJwtHead(String alg,String type){
        final JSONObject header = new JSONObject();
        header.put("alg", JwtTokenBuilder.ALG_SHA2);
        header.put("typ", "JWT");
        return header;
    }

    /**
     * 使用redis设置token有效时间
     * @param id 用户id
     * @param token 该用户的有效token
     */
    private void renewalToken(String id,String token){
        // 将当前用户的token 放到redis 设置在redis中的有效时间，这样 和 token有效时间是重复的
        redisTemplate.opsForValue().set(TOKEN_USER_ID_KEY + id, token , period, TimeUnit.SECONDS);
    }
}
